package com.weituotian.system.dao.impl;

import com.weituotian.core.jpa.domain.SimpleSpecification;
import com.weituotian.core.jpa.domain.Specification;
import com.weituotian.core.utils.PageInfo;
import com.weituotian.core.utils.Reflections;
import com.weituotian.system.dao.IBaseDao;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.util.Assert;

import javax.persistence.Query;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.*;
import java.io.Serializable;
import java.util.*;

@Repository
public abstract class BaseDao<T, PK extends Serializable> implements IBaseDao<T, PK> {

    @Autowired
    private SessionFactory sessionFactory;

    private Class<T> entityClass;

    protected abstract String getEntityName();

    protected abstract String getIdFieldName();

    @SuppressWarnings({"unchecked"})
    public BaseDao() {

        /*this.entityClass = null;
        Type type = getClass().getGenericSuperclass();
        if (type instanceof ParameterizedType) {
            Type[] parameterizedType = ((ParameterizedType) type).getActualTypeArguments();
            this.entityClass = (Class<T>) parameterizedType[0];
        }*/

        this.entityClass = Reflections.getClassGenricType(getClass());

    }

    public void setEntityClass(Class<T> entityClass) {
        this.entityClass = entityClass;
    }

    public Class<T> getEntityClass() {
        return entityClass;
    }

    public Session getSession() {
        return sessionFactory.getCurrentSession();
    }

    public Session openSession() {
        return sessionFactory.openSession();
    }

    public CriteriaBuilder getCriteriaBuilder() {
//        criteriaQuery.where(condition);
//        criteria.distinct(true);
        return getSession().getCriteriaBuilder();
    }

    /**
     * 生成一个typedQuery,已经是封装好所有的条件和排序的
     *
     * @param spec
     * @param pageInfo
     * @return
     */
    protected TypedQuery<T> getQuery(Specification<T> spec, PageInfo<?> pageInfo) {
        if (pageInfo != null) {
            return getQuery(spec, pageInfo.getOrderby());
        }
        return getQuery(spec, (Map<String, String>) null);
    }

    protected TypedQuery<T> getQuery(Specification<T> spec, Map<String, String> sort) {
        Session session = getSession();

        CriteriaBuilder builder = session.getCriteriaBuilder();
        CriteriaQuery<T> query = builder.createQuery(getEntityClass());


        //搜索条件
        Root<T> root = applySpecificationToCriteria(spec, builder, query);
        query.select(root);
        if (spec != null) {
            spec.beforeSelectList(root, query, builder);
        }

        //排序
        if (sort != null) {
            List<Order> orders = toOrders(sort, root, builder);
            spec.onOrder(orders, root, query, builder);//使扩展order方法
            query.orderBy(orders);
        }

        return session.createQuery(query);
    }

    protected TypedQuery<T> getQuery(Specification<T> spec) {
        return getQuery(spec, (Map<String, String>) null);
    }

    protected TypedQuery<Long> getCountQuery(Specification<T> spec) {
        Session session = getSession();

        CriteriaBuilder builder = session.getCriteriaBuilder();
        CriteriaQuery<Long> query = builder.createQuery(Long.class);

        Root<T> root = applySpecificationToCriteria(spec, builder, query);

        if (query.isDistinct()) {
            query.select(builder.countDistinct(root));
        } else {
            query.select(builder.count(root));
        }

        return session.createQuery(query);
    }

    /**
     * Applies the given {@link Specification} to the given {@link CriteriaQuery}.
     * 应用的 {@link Specification} 到 {@link CriteriaQuery}.
     *
     * @param spec  can be {@literal null}.
     * @param query must not be {@literal null}.
     * @return
     */
    protected <S> Root<T> applySpecificationToCriteria(Specification<T> spec, CriteriaBuilder builder, CriteriaQuery<S> query) {

        Assert.notNull(query);
        Root<T> root = query.from(getEntityClass());

        if (spec == null) {
            return root;
        }


//        CriteriaBuilder builder = getSession().getCriteriaBuilder();
        Predicate predicate = spec.toPredicate(root, query, builder);

        if (predicate != null) {
            query.where(predicate);
        }

        return root;
    }

    public List<javax.persistence.criteria.Order> toOrders(Map<String, String> sort, Root<T> root, CriteriaBuilder cb) {

        List<javax.persistence.criteria.Order> orders = new ArrayList<javax.persistence.criteria.Order>();

        if (sort == null) {
            return orders;
        }

        Assert.notNull(cb);

        for (Map.Entry<String, String> entry : sort.entrySet()) {
            String field = entry.getKey();
            String direction = entry.getValue();
            javax.persistence.criteria.Order order = null;
            if (direction.equals("asc")) {
                order = cb.asc(root.get(field));
            } else if (direction.equals("desc")) {
                order = cb.desc(root.get(field));
            }
            orders.add(order);
        }

        return orders;
    }

    public void flush() {
        getSession().flush();
    }

    /*public <S extends T> S save(S entity) {
        return (S) getSession().merge(entity);
    }*/

    public void persist(T entity) {
        getSession().persist(entity);
    }

    public T merge(T entity) {
        return (T) getSession().merge(entity);
    }

    public PK save(T entity) {
        return (PK) getSession().save(entity);
    }

    public void saveOrUpdate(T entity) {
        getSession().saveOrUpdate(entity);
    }

    public void update(T entity) {
        getSession().update(entity);
    }

    public T findOne(PK id) {

        Assert.notNull(id, "id不能够为空");

        return getSession().find(entityClass, id);
    }

    public List<T> findAll() {
        return getQuery(null, (PageInfo<T>) null).getResultList();
    }

    public List<T> findIds(PK[] ids) {
        String idFieldName = getIdFieldName();
        if (ids == null || ids.length == 0) {
            return Collections.emptyList();
        }

        List<PK> idList = Arrays.asList(ids);

        ByIdsSpecification<T> specification = new ByIdsSpecification<T>(idFieldName);
        TypedQuery<T> query = getQuery(specification, (PageInfo<T>) null);

        return query.setParameter(specification.parameter, idList).getResultList();
    }

    public void delete(PK id) {

        Assert.notNull(id, "id不能够为空");

        T entity = findOne(id);

        delete(entity);

        /*if (entity != null) {
        }*/
    }

    public void deleteBatch(PK[] ids) {
        String queryString = String.format(DELETE_ALL_QUERY_STRING, getEntityName());

        StringBuilder builder = new StringBuilder(queryString);
        builder.append(" where");

        for (int i = 0; i < ids.length; i++) {
            if (i == 0) {
                builder.append(" id=");
            } else {
                builder.append(" or id=");
            }
            builder.append(ids[i]);
        }
        Session session = this.getSession();
        Query q = session.createQuery(builder.toString());
        q.executeUpdate();
    }

    public void delete(T entity) {
        Session session = getSession();
        Assert.notNull(entity, "要删除的实体为空");
        session.remove(session.contains(entity) ? entity : session.merge(entity));
    }

    public List<T> findPage(Specification<T> spec, PageInfo<?> pageInfo) {
        TypedQuery<T> query = getQuery(spec, pageInfo);

        //分页
        query.setFirstResult(pageInfo.getFrom());
        query.setMaxResults(pageInfo.getPagesize());

        //总数
        Long total = executeCountQuery(getCountQuery(spec));
        pageInfo.setTotal(total.intValue());

        //结果list
        List<T> content = total > pageInfo.getFrom() ? query.getResultList() : Collections.<T>emptyList();

        return content;
    }

    public Long executeCountQuery(TypedQuery<Long> query) {

        Assert.notNull(query);

        List<Long> totals = query.getResultList();
        Long total = 0L;

        for (Long element : totals) {
            total += element == null ? 0 : element;
        }

        return total;
    }

    private static final class ByIdsSpecification<T> extends SimpleSpecification<T> {

        String idFieldName;
        ParameterExpression<Iterable> parameter;

        public ByIdsSpecification(String idFieldName) {
            this.idFieldName = idFieldName;
        }

        public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb) {

            Path<?> path = root.get(idFieldName);
            parameter = cb.parameter(Iterable.class);
            return path.in(parameter);
        }
    }
}
